import base64
import json
import pickle

from django.http import JsonResponse, HttpResponseForbidden
from django.shortcuts import render

# Create your views here.
from django.views import View
from django_redis import get_redis_connection

from apps.goods.models import SKU


class CartsView(View):
    """购物车管理：增删改查
    增：POST /carts/
    查：GET /carts/
    改：PUT /carts/
    删：DELETE /carts/
    """

    def post(self, request):
        """实现新增购物车的逻辑"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')
        count = json_dict.get('count')
        # 如果前端没有传递selected参数,我们给个默认值True,默认被勾选
        selected = json_dict.get('selected', True)

        # 校验参数
        if not all([sku_id, count]):
            return JsonResponse({'code': 400, 'errmsg': '缺少必传参数'})
        # 判断sku_id是否存在
        try:
            SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return JsonResponse({'code': 400, 'errmsg': '参数sku_id不存在'})
        try:
            # 使用int类型强转count,如果count不是数字,自动会抛出异常
            count = int(count)
        except Exception:
            return JsonResponse({'code': 400, 'errmsg': '参数count错误'})
            # 校验selected类型
        if not isinstance(selected, bool):
            return JsonResponse({'code': 400, 'errmsg': '参数selected错误'})

        # 实现核心逻辑:登录用户和未登录用户新增购物车
        # 判断用户是否登录
        if request.user.is_authenticated:
            # 如果用户已登录,新增redis购物车
            # hash：carts_user_id: {sku_id1: count1, sku_id2: count2, ...}
            # set：selected_user_id: [sku_id1]
            # 创建连接到redis5号库的对象
            redis_conn = get_redis_connection('carts')
            pl = redis_conn.pipeline()

            user_id = request.user.id
            # 操作hash，增量存储sku_id和count
            pl.hincrby('carts_%s' % user_id, sku_id, count)
            # 操作set，如果selected为True，需要将sku_id添加到set
            if selected:
                # 新增选中的状态
                pl.sadd('selected_%s' % user_id, sku_id)
            # 执行管道
            pl.execute()

            return JsonResponse({'code': 0, 'errmsg': 'OK'})
        else:
            # 如果用户未登录,新增cookie购物车
            # 1.从cookie中读取购物车字典
            # 从cookie中读取之前保存的购物车密文字符串
            cart_str = request.COOKIES.get('carts')
            if cart_str:
                # 将密文字符串转成bytes类型的密文字符串
                cart_str_bytes = cart_str.encode()
                # 将bytes类型的密文字符串使用base64解码为bytes类型字典
                cart_str_bytes = base64.b64decode(cart_str_bytes)
                # 将bytes类型字典使用pickle反序列化为python字典
                cart_dict = pickle.loads(cart_str_bytes)
            else:
                # 用户从没有操作过 cookie 购物车
                cart_dict = {}
            """
            # 判断要加入购物车的商品是否已经在购物车中
            # 如有相同商品，累加求和，反之，直接赋值
            # 我们判断用户之前是否将该商品加入过购物车, 如果加入过
            # 则只需要让数量增加即可. 
            # 如果没有存在过, 则需要创建, 然后增加
            {
                "sku_id1":{
                    "count":"1",
                    "selected":True
                },
                "sku_id3":{
                    "count":"3",
                    "selected":False
                }
            }
            """
            # 2.添加购物车数据到购物车字典
            if sku_id in cart_dict:
                # 如果要添加的商品再购物车已存在, 累加数量
                origin_count = cart_dict[sku_id]['count']
                # count = origin_count + count
                count += origin_count
            #     cart_dict[sku_id] = {
            #         'count': count,
            #         'selected': selected
            #     }
            # else:
            #     # 如果要添加的商品再购物车不存在, 直接新增记录
            #     cart_dict[sku_id] = {
            #         'count': count,
            #         'selected': selected
            #     }
            # 优化代码:
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }

            # 3.购物车字典转字符串并写入到cookie
            # 先使用pickle将cart_dict序列化为bytes类型的字典
            cart_dict_bytes = pickle.dumps(cart_dict)
            # 再使用base64将bytes类型的字典编码为bytes类型的密文字符串
            cart_str_bytes = base64.b64encode(cart_dict_bytes)
            # 然后再将bytes类型的密文字符串转成真的字符串
            cookie_cart_str = cart_str_bytes.decode()

            # # 合并为一行代码:
            # # 将字典转成 bytes,再将 bytes 转成 base64 的 bytes
            # # 最后将bytes转字符串
            # cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()

            # 最后将密文字符串写入到cookie
            response = JsonResponse({'code': 0, 'errmsg': 'ok'})
            response.set_cookie('carts', cookie_cart_str)
            return response

    def get(self, request):
        """实现查询购物车的逻辑"""
        # 判断用户是否登录
        if request.user.is_authenticated:
            # 如果用户已登录，查询redis购物车
            user_id = request.user.id
            redis_conn = get_redis_connection('carts')
            # 查询hash中的商品和数量 redis_cart = {b'sku_id1': b'coun11', b'sku_id2': b'coun2'}
            redis_cart = redis_conn.hgetall('carts_%s' % user_id)
            # 查询set中的勾选状态 redis_selected = [b'sku_id1']
            redis_selected = redis_conn.smembers('selected_%s' % user_id)

            # 将redis购物车转成可操作对象:将redis_cart和redis_selected里面的数据合并到一个购物车字典中
            # 使用redis_cart和redis_selected构造一个跟cookie购物车字典一样的字典
            # 方便统一查询
            cart_dict = {}
            for sku_id, count in redis_cart.items():
                cart_dict[int(sku_id)] = {
                    'count': int(count),
                    # 如果sku_id再redis_selected集合中返回True,反之False
                    'selected': sku_id in redis_selected
                }

        else:
            # 如果用户未登录，查询cookie购物车
            # 从cookie中读取购物车字典
            cart_str = request.COOKIES.get('carts')
            if cart_str:
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart_dict = {}

        # 读取购物车里面的商品信息
        # 提取字典中的所有key
        sku_ids = cart_dict.keys()
        # 通过SKU商品表根据条件id是否在sku_ids中查询模型类中sku列表信息
        sku_model_list = SKU.objects.filter(id__in=sku_ids)
        cart_skus = []
        for sku in sku_model_list:
            cart_skus.append({
                'id': sku.id,
                'name': sku.name,
                'default_image_url': sku.default_image.url,
                'price':sku.price,
                'count': cart_dict.get(sku.id).get('count'),
                'selected': cart_dict.get(sku.id).get('selected'),
                'amount': sku.price * cart_dict.get(sku.id).get('count'),
            })

        # 响应结果(渲染购物车页面)
        return JsonResponse({
            'code': 0,
            'errmsg': 'ok',
            'cart_skus': cart_skus
        })

    def put(self, request):
        """实现修改购物车的逻辑
        修改购物车和新增购物车百分之九十九是相同的
        """
        # 接收参数
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')
        count = json_dict.get('count')
        # 如果前端没有传递selected参数，我们给个默认值True,默认被勾选
        selected = json_dict.get('selected', True)

        # 校验参数
        if not all([sku_id, count]):
            return JsonResponse({'code': 400, 'errmsg': '缺少必传参数'})
        try:
            SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return JsonResponse({'code': 400, 'errmsg': '参数sku_id错误'})
        try:
            # 使用int类型强转count，如果count不是数字，自动会抛出异常
            count = int(count)
        except Exception:
            return JsonResponse({'code': 400, 'errmsg': '参数count错误'})
        # 校验selected类型
        if not isinstance(selected, bool):
            return JsonResponse({'code': 400, 'errmsg': '参数selected错误'})

        # 判断用户是否登录
        if request.user.is_authenticated:
            # 如果用户已登录，修改redis购物车
            # 由于前端向后端发送的是修改后的数据,所以后端直接覆盖写入即可
            user_id = request.user.id
            redis_conn = get_redis_connection('carts')
            pl = redis_conn.pipeline()
            pl.hset('carts_%s' % user_id, sku_id, count)
            if selected:
                pl.sadd('selected_%s' % user_id, sku_id)
            else:
                pl.srem('selected_%s' % user_id, sku_id)
            pl.execute()

            # 在修改成功后，记得将修改后的数据返回给前端，实现局部刷新的效果
            cart_sku = {
                'id': sku_id,
                'count': count,
                'selected': selected
            }

            return JsonResponse({'code': 0, 'errmsg': 'OK', 'cart_sku': cart_sku})
        else:
            # 如果用户未登录，修改cookie购物车
            # 从cookie中读取购物车数据
            cart_str = request.COOKIES.get('carts')
            if cart_str:
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart_dict = {}

            # 修改购物车字典
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }

            # 将购物车字典转字符串并写入到cookie
            cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()

            # 构建响应数据
            cart_sku = {
                'id': sku_id,
                'count': count,
                'selected': selected
            }

            # 创建响应对象
            response = JsonResponse({
                'code': 0,
                'errmsg': 'ok',
                'cart_sku': cart_sku
            })
            # 响应结果并将购物车数据写入到cookie
            response.set_cookie('carts', cookie_cart_str)
            return response

    def delete(self, request):
        """实现删除购物车逻辑"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')

        # 校验参数
        try:
            SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return JsonResponse({
                'code': 0,
                'errmsg': '商品不存在'
            })

        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 如果用户已登录,删除redis购物车
            user_id = user.id
            # 创建链接到redis的对象
            redis_conn = get_redis_connection('carts')
            pl = redis_conn.pipeline()
            # 删除hash中的购物车数据
            # 删除键，就等价于删除了整条记录
            pl.hdel('carts_%s' % user_id, sku_id)
            # 删除set中的勾选状态
            pl.srem('selected_%s' % user_id, sku_id)
            pl.execute()

            return JsonResponse({
                'code': 0,
                'errmsg': 'ok'
            })
        else:
            # 如果用户未登录,删除cookie购物车
            # 从cookie中读取数据
            cart_str = request.COOKIES.get('carts')
            if cart_str:
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart_dict ={}

            # 创建响应对象
            response = JsonResponse({'code': 0, 'errmsg': 'OK'})

            # 删除购物车字典中的key
            # 只能删除存在的key,如果删除不存在的key会抛出异常
            if sku_id in cart_dict:
                del cart_dict[sku_id]

                # 将购物车字典转字符串并写入到cookie
                cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
                # 响应结果并将购物车数据写入到cookie
                response.set_cookie('carts', cookie_cart_str)
            return response


class CartSelectAllView(View):
    """全选购物车"""

    def put(self, request):
        """实现购物车全选"""
        #接收参数
        json_dict = json.loads(request.body.decode())
        selected = json_dict.get('selected', True)

        # 校验参数
        if selected:
            if not isinstance(selected, bool):
                return HttpResponseForbidden('selected不是bool类型')

        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 如果已登录,全选redis购物车
            redis_conn = get_redis_connection('carts')
            # 读取hash中所有的sku_id {b'sku_id1':b'count1', ...}
            redis_cart = redis_conn.hgetall('carts_%s' % user.id)
            # 读取字典中所有的sku_id
            sku_ids = redis_cart.keys()
            # 确定全选或取消全选
            if selected:
                # 确定全选:将sku_ids中所有的sku_id添加到set
                redis_conn.sadd('selected_%s' % user.id, *sku_ids)
            else:
                # 取消全选:将sku_ids中所有的sku_id从set移除
                redis_conn.srem('selected_%s' % user.id, *sku_ids)

            return JsonResponse({'code': 0, 'errmsg': 'ok'})
        else:
            # 如果未登录,全选cookie购物车
            # 从cookie中读取购物车字典
            cart_str = request.COOKIES.get('carts')
            response = JsonResponse({'code': 0, 'errmsg': '全选购物车成功'})
            if cart_str:
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))

                # 将购物车字典中的所有selected字段设置为True或False
                for sku_id, dict in cart_dict.items():
                    cart_dict[sku_id]['selected'] = selected

                # 将购物车字典转字符串并写入到cookie
                cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
                response.set_cookie('carts', cookie_cart_str)

            return response


class CartsSimpleView(View):
    """商品页面右上角购物车"""

    def get(self, request):

        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录，查询 Redis 购物车
            redis_conn = get_redis_connection('carts')
            item_dict = redis_conn.hgetall('carts_%s' % user.id)
            cart_selected = redis_conn.smembers('selected_%s' % user.id)

            # 将 redis 中的两个数据统一格式，跟 cookie 中的格式一致，方便统一查询
            cart_dict = {}
            for sku_id, count in item_dict.items():
                cart_dict[int(sku_id)] = {
                    'count': int(count),
                    'selected': sku_id in cart_selected
                }
        else:
            # 用户未登录，查询 cookie 购物车
            cookie_cart = request.COOKIES.get('carts')
            if cookie_cart:
                cart_dict = pickle.loads(base64.b64decode(cookie_cart.encode()))
            else:
                cart_dict = {}

        # 构造简单购物车 JSON 数据
        cart_skus = []
        sku_ids = cart_dict.keys()
        skus = SKU.objects.filter(id__in=sku_ids)
        for sku in skus:
            cart_skus.append({
                'id': sku.id,
                'name': sku.name,
                'count': cart_dict.get(sku.id).get('count'),
                'default_image_url': sku.default_image.url
            })

        # 响应 json 列表数据
        return JsonResponse({'code': 0,
                            'errmsg': 'OK',
                            'cart_skus': cart_skus})
